Gli articoli di EAR - Didattica
Ma prima vediamo un po' come risolvere l'esercizio assegnato la volta scorsa; se ben ricordate scopo del programma era quello di far indovinare un numero all'utente, fornendo al massimo 10 possibilità, ed ogni volta che l'utente ha fornito un'indicazione specificare, se non ha indovinato, se il numero inserito è superiore o inferiore a quello pensato. Dunque facciamo un analisi del problema; ovviamente per "pensare" un numero, utilizziamo la funzione random(100) (che fornisce un numero casuale da 1 a 100, in realtà questa funzione non esiste, ma supponiamo pure che ci sia); di seguito verrà un ciclo che al massimo viene eseguito 10 volte, ma può anche uscire prima (se l'utente indovina il numero); quindi scriviamo la pseudocodifica del programma:
a = random(100) cicla 10 volte . preleva il numero dall'esterno e memorizzalo in b . se b "minore di" a allora stampa "Il numero inserito è minore di quello pensato" . altrimenti se b "maggiore di" a allora stampa "Il numero inserito è maggiore di quello pensato" . altrimenti . stanpa "Indovinato!!!!" . esci dal ciclo fine ciclo se non indovinato allora stampa "Numero tentativi esauriti"
E tutto va bene, l'unico punto oscuro sta nel come determinare all'uscita del ciclo se l'utente non ha indovinato e quindi stampare il messaggio "Numero tentativi esauriti"; possiamo utilizzare un'altra variabile (oltre ad un indice che conteggia il numero dei tentativi) che indica se l'utente ha indovinato o meno; a questo punto possiamo stendere una prima versione del programma:
#include "minore di"stdio.h"maggiore di" main() { int a, b, i, ind; /* Pensa un numero */ a = Random(100); /* ind a 1 indica che l'utente ha indovinato */ ind = 0; /* i conteggia il numero di tentativi */ i = 1; while (i "minore di"= 10 && ind == 0) { printf("Inserire un numero da 1 a 100 - "); scanf("%d", & b); if (b "minore di" a) printf("Il numero inserito è minore di quello pensato\n"); else if (b "maggiore di" a) printf("Il numero inserito è maggiore di quello pensato\n"); else { printf("Indovinato!!!!\ N"); ind = 1; } /* Incrementa il numero di tentativi */ i++; } if (ind == 0) printf("Numero tentativi esauriti\n"); }
Tuttavia il listato può essere ulteriormente ottimizzato; si può infatti eliminare l'utilizzo della variabile ind, impostando direttamente i quando si vuole uscire dal ciclo; naturalmente permane il problema a questo punto, di come stabilire all'uscita del ciclo, se l'utente ha indovinato o meno; possiamo impostare i in modo da distinguere se il ciclo sia terminato per aver superato i 10 tentativi; in tal caso infatti i vale 11, se impostiamo i a 100 per uscire, possiamo determinare i due differenti casi:
#include "minore di"stdio.h"maggiore di" main() { . int a, b, i; . /* Pensa un numero */ . a = Random(100); . /* i conteggia il numero di tentativi */ . i = 1; . while (i "minore di"= 10) . { . printf("Inserire un numero da 1 a 100 - "); scanf("%d", & b); . if (b "minore di" a) printf("Il numero inserito è minore di quello pensato\n"); . else if (b "maggiore di" a) printf("Il numero inserito è maggiore di quello pensato\n"); . else . { . printf("Indovinato!!!!\n"); . i = 100; . } . /* Incrementa il numero di tentativi */ . i++; . } . if (i "minore di" 100) printf("Numero tentativi esauriti\n"); }
Tutti i programmi che abbiamo visto fino ad adesso sono totalmente risolti nel main(); naturalmente è però possibile suddividere il programma in diversi blocchi denominati funzioni, e questo per una serie di buoni motivi; primo perché se capita di dover risolvere un sotto-problema più volte, può essere utile scorporare questo sotto-problema in una funzione separata (passando i parametri necessari) e richiamare la funzione più volte (esempi di funzioni lo abbiamo già utilizzato, come ad esempio printf, scanf ecc... ). E' quindi ovvio che l'utilizzo delle funzioni rende più efficiente e più chiara la stesura del programma e si inserisce agevolmente nella tecnica di analisi top-down. Per dichiarare una nuova funzione occorre seguire la seguente sintassi: tipo-ritornato nome-funzione(dichiarazione argomenti) { . dichiarazioni ed istruzioni } Non è possibile definire funzioni nelle funzioni come accade in Pascal o in altri linguaggi strutturati a blocchi. tipo-ritornato è il tipo del valore ritornato dalla funzione; se la funzione non ritorna alcun valore si deve indicare void; se non si specifica il tipo-ritornato viene considerato come default int; nome-funzione è il nome della funzione definita e viene utilizzato per il richiamo; dichiarazione argomenti è la lista degli argomenti passati alla funzione, separati da virgola; il tipo degli argomenti passati può essere indicato nella lista o successivamente; per uscire dalla funzione si può utilizzare l'istruzione return, seguito dalla variabile o dall'espressione il cui valore è quello da ritornare (nel caso la funzione non restituisca alcun valore non occorre specificare nulla); allora vediamo qualche esempio:
#include "minore di"stdio.h"maggiore di" #include "minore di"math.h"maggiore di" double Quadrato(double x) { . return(x*x) } int sommatoria(num) int num; { . int i, tot = 0; . for (i=1; i"minore di"=num; i++) . tot += i; . return(tot); } int main() { printf("Il quadrato di 10 è %f\n", Quadrato(10)); printf("La sommatoria da 1 a 10 è %d\n", sommatoria(10)); }
Come vedete dall'esempio la scrittura di una funzione è identica a quella del main() che abbiamo utilizzato fino ad adesso; una differenza rilevante, la potete notare tra la funzione Quadrato e la funzion sommatoria; infatti nella prima il tipo di variabile dei parametri viene specificata sulla linea di dichiarazione, mentre nella seconda sulle linee successive (in questo caso è possibile inserire la dichiarazione su più linee), mentre sulla linea di dichiarazione della funzione vengono inseriti solo i nomi dei parametri, divisi sempre dalla virgola). Un altro aspetto interessante è la definizione dei prototipi di funzione; supponiamo infatti, per come è predisposto il programma, che una funzione venga chiamata da qualche parte prima che la stessa venga definita; in tal caso il compilatore darà errore; questo è un motivo per l'utilizzo dei prototipi di funzione; questi non sono altro che la semplice dichiarazione della struttura di una funzione, da inserire all'inizio del sorgente, per istruire il compilatore sull'esistenza delle funzioni che verranno definite di seguito; è buona norma, anche se non è necessario, definire sempre i prototipi di funzione (ed inserirli in un file di inclusione); facciamo l'esempio con le funzioni prima definite:
double Quadrato(double x); int sommatoria (int num);
oppure
double Quadrato(double); int sommatoria (int);
Come potete osservare non è necessario specificare il nome dei parametri; se dovesse esserci una discrepanza tra la definizione del prototipo di una funzione e la funzione stessa, il compilatore darà errore.
Tutte le variabili che siamo riusciti a definire, hanno una validità limitata, cioè possono essere utilizzate e viste solo dalla funzione in cui sono dichiarate, e per questo si dicono interne; è possibile tuttavia definire delle variabili "esterne" cioè che vengono definite alla partenza del programma, hanno validità per tutto il tempo di esecuzione del programma e, cosa più importante, sono viste e possono essere utilizzate da tutte le funzioni del programma; per definire queste variabili si utilizza la normale sintassi di dichiarazione, salvo che va effettuato fuori dalle funzioni; esempio:
#include "minore di"stdio.h"maggiore di" int v1, v3; int x; void pippo1() { . x = v1 * v3; . return; } int main() { . v1 = 10; v2 = 5; . pippo1(); . printf("%d\n", x); . return; }
Se c'é un gran quantitativo di parametri da scambiare tra diverse funzioni, l'utilizzo delle variabili esterne dovrebbe essere preferita a quello del passaggio in linea dei parametri di funzione; tuttavia si consiglia di utilizzare le variabili esterne come scambio di informazioni tra funzioni il meno possibile, perché questo rende meno indipendente l'esecuzione di una funzione dall'ambiente esterno, e quindi può renderla meno efficace se si tenta di eseguirla in diversi ambienti; quindi il listato dell'esempio precedente non dovrebbe essere risolto nella maniera vista, ma tramite il passaggio dei parametri in linea; del resto anche in questa, come in ogni altra situazione, l'utilizzo delle variabili esterne dipende dall'esigenza. Un ultima cosa sull'inizializzazione delle variabili, se essa non viene specificata in fase di dichiarazione (come ad esempio int ex=0; ) nel caso di variabili esterne, queste verranno inizializzate a 0 automaticamente; per le variabili locali o interne, non verranno inizializzate (a meno che non venga espressamente richiesto come nell'esempio di sopra), quindi non si potrà stabilire a priori il loro contenuto; morale della favola: inizializzate sempre le variabili prima di utilizzarle, indipendentemente che siano esterne o meno. Anche per oggi è tutto, vi diamo appuntamento al prossimo mese in cui continueremo a parlare di funzioni e di tanto altro.